package org.byqh.dqeai.service;

import lombok.extern.slf4j.Slf4j;
import net.jpountz.xxhash.XXHashFactory;
import org.apache.tomcat.util.http.fileupload.FileUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.server.ResponseStatusException;

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.FileTime;
import java.time.Instant;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Service
@Slf4j
public class UploadService {
    @Value("${upload.path}")
    private String uploadPath;

    public static final String TEMP = "temp";
    public static final String TOUCH = "touch";

    private XXHashFactory factory = XXHashFactory.fastestInstance();

    /**
     * 确认文件所在的文件夹存在, 不存在则创建
     *
     * @param path
     * @throws IOException
     */
    private static void ensureDirectory(Path path) throws IOException {
        Path directory = path.getParent();
        if (!Files.exists(directory)) {
            Files.createDirectories(directory);
        }
    }

    /**
     * 创建touch文件
     *
     * @param path
     * @throws IOException
     */
    private static void touch(final Path path) throws IOException {
        ensureDirectory(path);

        if (Files.exists(path)) {
            Files.setLastModifiedTime(path, FileTime.from(Instant.now()));
        } else {
            Files.createFile(path);
        }
    }

    public String longToHex(long x) {
        byte[] bytes = toByteArray(x);
        StringBuilder sb = new StringBuilder();

        for (int i = bytes.length - 1; i >= 0; i--) {
            sb.append(String.format("%02X", bytes[i]));
        }
        return sb.toString();
    }

    public static byte[] toByteArray(long value) {
        byte[] result = new byte[8];
        for (int i = 7; i >= 0; i--) {
            result[i] = (byte) (value & 0xffL);
            value >>= 8;
        }
        return result;
    }

    public String getHash(byte[] bytes) {
        return longToHex(factory.hash64().hash(bytes, 0, bytes.length, 0));
    }

    private Path getChunkPath(String fileHash, Integer chunk) {
        return Paths.get(this.uploadPath, fileHash, TEMP, chunk.toString());
    }

    private Path getChunkTouchPath(String fileHash, Integer chunk, String hash) {
        return Paths.get(this.uploadPath, fileHash, TOUCH, hash);
    }

    private Path getFileTouchPath(String fileHash) {
        return Paths.get(this.uploadPath, fileHash, TOUCH, fileHash);
    }

    public Path getFilePath(String fileHash) {
        return Paths.get(this.uploadPath, fileHash, fileHash);
    }

    public void upload(MultipartFile multipartFile, String fileHash, Integer chunk, String hash) throws IOException {
        byte[] bytes = multipartFile.getBytes();
        String hash3 = getHash(bytes);

        if (!hash.equals(hash3))
            throw new IllegalArgumentException("hash value not match");

        Path path = getChunkPath(fileHash, chunk);
        ensureDirectory(path);

        try (OutputStream os = new FileOutputStream(path.toString())) {
            os.write(bytes);
        }

        touch(getChunkTouchPath(fileHash, chunk, hash));
    }

    public boolean checkFile(String fileHash) throws IOException {
        return Files.exists(getFileTouchPath(fileHash))
                && Files.exists(getFilePath(fileHash))
                && Files.size(getFilePath(fileHash)) > 0;
    }

    public boolean checkFileChunk(String fileHash, Integer chunk, String hash) throws IOException {
        return Files.exists(getChunkPath(fileHash, chunk))
                && Files.exists(getChunkTouchPath(fileHash, chunk, hash))
                && Files.size(getChunkPath(fileHash, chunk)) > 0;
    }

    public byte[] download(String fileHash, Integer chunk, Integer chunkSize) throws IOException {
        Path realFile = this.getFilePath(fileHash);

        if (!Files.exists(realFile)) {
            throw new ResponseStatusException(HttpStatus.NOT_FOUND, "文件不存在!");
        }

        // 4.验证文件大小
        long fileLength = Files.size(realFile);

        if (fileLength == 0) {
            throw new ResponseStatusException(HttpStatus.NOT_FOUND, "文件不存在!");
        }

        boolean isEnd = false;
        if ((chunk + 1) * chunkSize >= fileLength) {
            isEnd = true;
        }

        try (RandomAccessFile raFile = new RandomAccessFile(realFile.toString(), "r")) {
            raFile.seek(chunk * chunkSize);

            long blockSize = chunkSize;

            if (isEnd) {
                blockSize = fileLength - chunk * chunkSize;
            }

            int size = (int) blockSize;
            byte[] buf = new byte[size];
            raFile.read(buf);

            log.info("下载文件分片: " + buf.length);

            return buf;
        }
    }

    public boolean combine(String fileHash, String filename) throws IOException {
        //Path touchFile = Paths.get(this.uploadPath, guid, guid);
        Path realFile = Paths.get(this.uploadPath, fileHash, fileHash);
        Path tempPath = Paths.get(this.uploadPath, fileHash, TEMP);

        Files.deleteIfExists(realFile);

        FileOutputStream os = null;// 文件追加写入
        FileChannel fcin = null;
        FileChannel fcout = null;
        try {
            String logInfo = "[ 文件名称：" + filename + " ，HASH值：" + fileHash + " ]";

            log.info("合并文件——开始 " + logInfo);
            os = new FileOutputStream(realFile.toString(), true);
            fcout = os.getChannel();
            if (Files.exists(tempPath)) {
                try (Stream<Path> stream = Files.list(tempPath)) {
                    List<Path> tempFiles = stream
                            .filter(p -> !Files.isDirectory(p))
                            .sorted((o1, o2) -> {
                                if (Integer.parseInt(o1.getFileName().toString()) < Integer.parseInt(o2.getFileName().toString())) {
                                    return -1;
                                }

                                if (Integer.parseInt(o1.getFileName().toString()) == Integer.parseInt(o2.getFileName().toString())) {
                                    return 0;
                                }
                                return 1;
                            })
                            .collect(Collectors.toList());

                    //每次读取10MB大小，字节读取
                    //byte[] byt = new byte[10 * 1024 * 1024];
                    //int len;
                    //设置缓冲区为10MB
                    ByteBuffer buffer = ByteBuffer.allocate(10 * 1024 * 1024);
                    for (int i = 0; i < tempFiles.size(); i++) {
                        FileInputStream fis = new FileInputStream(tempFiles.get(i).toString());

                        fcin = fis.getChannel();
                        if (fcin.read(buffer) != -1) {
                            buffer.flip();
                            while (buffer.hasRemaining()) {
                                fcout.write(buffer);
                            }
                        }
                        buffer.clear();
                        fis.close();
                        //删除分片
                        //Files.deleteIfExists(tempFiles.get(i));
                    }
                    os.close();

                    //删除临时目录
                    FileUtils.deleteDirectory(tempPath.toFile());

                    //创建标识文件
                    touch(getFileTouchPath(fileHash));

                    log.info("文件合并——结束 " + logInfo);
                    return true;
                }
            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            log.error("文件合并——失败 " + e.getMessage());
        }
        return false;
    }
}
